/**
* @file Sound.cpp
* @author Max Ocklind
* @version 2010-01-10
*
* An easy-to-use wrapper for an OpenAL sound source with some extra sound properties:</br>
* * sound priprity</br>
* * distance culling type</br>
* * sound type</br>
* * sound effect filter with an associated timer
*
* <p><b>Note:</b> if the default constructor is used the <code>Sound</code> object must be reseted
* (provided with a sound source) before it can be used!</p>
**/


/* includes */

#include "Sound.h"


/* functions */

/* sound source property functions */

/**
* Sets the specified sound volume (gain) of the sound source.
*
* @param gain the volume (gain) to be set (0.0 - 1.0)
*/
void Sound::setVolume(ALfloat gain)
{
	alSourcef(mSource, AL_GAIN, gain);
	errCheck->checkALError("Sound", "setVolume");
}

/**
* Returns the sound volume (gain) of the sound source.
*
* @return the sound volume (gain) as a float
*/
ALfloat Sound::getVolume()
{
	ALfloat gain;
	alGetSourcef(mSource, AL_GAIN, &gain);
	errCheck->checkALError("Sound", "getVolume");
	
	return gain;
}

/**
* Sets the basic sound properties of the sound source.
*
* @param position pointer to floating point-vector
* @param velocity pointer to floating point-vector
* @param loop indicates if the sound should loop or not
* @param pitch the pitch of the sound (0.0 - ...)
* @param gain the gain (volume contribution, 0.0 - 1.0) to the over all sound output
* @param rolloffFactor specifies how fast the sound should fade with distance (0.0 - ...)
*/
void Sound::setProperties(ALfloat* position, ALfloat* velocity, ALboolean loop, ALfloat pitch, ALfloat gain, ALfloat rolloffFactor)
{
	updatePositionAndVelocity(position, velocity);

	alSourcei(mSource, AL_LOOPING, loop);
	errCheck->checkALError("Sound", "setProperties (loop)");

	alSourcef(mSource, AL_PITCH, pitch);
    errCheck->checkALError("Sound", "setProperties (pitch)");
	
	alSourcef(mSource, AL_GAIN, gain);
	errCheck->checkALError("Sound", "setProperties (gain)");

	alSourcef(mSource, AL_ROLLOFF_FACTOR, rolloffFactor);
	errCheck->checkALError("Sound", "setProperties (roll of factor)");
		
	alSourcei(mSource, AL_SOURCE_RELATIVE, AL_FALSE);
	errCheck->checkALError("Sound", "setProperties (source realative)");
}

/**
* Sets the position and velocity properties of the sound source.
*
* @param position pointer to floating point-vector
* @param velocity pointer to floating point-vector 
*/
void Sound::updatePositionAndVelocity(ALfloat *position, ALfloat *velocity)
{
	alSourcefv(mSource, AL_POSITION, position);
	errCheck->checkALError("Sound", "updatePositionAndVelocity (position)");

	alSourcefv(mSource, AL_VELOCITY, velocity);
	errCheck->checkALError("Sound", "updatePositionAndVelocity (velocity)");
}


/* sound source functions */

/**
* Plays the sound loaded in the specified buffer with default values and without any sound effect
* filter enabled.
*
* @param buffer the sound buffer of the sound to be played
* @param priority the priority of the sound to be played
* @param cullType the culling type of the sound to be played
* @param position pointer to floating point-vector
* @param velocity pointer to floating point-vector
* @throws string if the specified buffer is invalid
*/
void Sound::play(SoundBuffer buffer, int priority, const char* cullType, ALfloat* position, ALfloat* velocity)
{
	play(buffer, priority, cullType, position, velocity, DEFAULT_AL_LOOP, DEFAULT_AL_PITCH, DEFAULT_AL_GAIN, DEFAULT_AL_ROLLOFF_FACTOR, NULL, 0);
}

/**
* Plays the sound loaded in the specified buffer with default values and with the specified sound
* effect filter enabled for the specified number of seconds.
*
* @param buffer the sound buffer of the sound to be played
* @param priority the priority of the sound to be played
* @param cullType the culling type of the sound to be played
* @param position pointer to floating point-vector
* @param velocity pointer to floating point-vector
* @param filter a pointer to the sound effect filter that should be enabled on the source, 
* <code>NULL</code> is used to play source without any sound effect filter enabled
* @param timeLimit the time in seconds that the sound effect filter should be enabled
* @throws string if the specified buffer is invalid
*/
void Sound::play(SoundBuffer buffer, int priority, const char* cullType, ALfloat* position, ALfloat* velocity, SFXFilter* filter, unsigned long timeLimit)
{
	play(buffer, priority, cullType, position, velocity, DEFAULT_AL_LOOP, DEFAULT_AL_PITCH, DEFAULT_AL_GAIN, DEFAULT_AL_ROLLOFF_FACTOR, filter, timeLimit);
}

/**
* Plays the sound loaded in the specified buffer with specified values and with the specified sound
* effect filter enabled for the specified number of seconds.
*
* @param buffer the sound buffer of the sound to be played
* @param priority the priority of the sound to be played
* @param cullType the culling type of the sound to be played
* @param position pointer to floating point-vector
* @param velocity pointer to floating point-vector
* @param loop indicates if the sound should loop or not
* @param pitch the pitch of the sound (0.0 - ...)
* @param gain the gain (contribution; 0.0 - 1.0) to the over all sound output
* @param rolloffFactor specifies how fast the sound should fade with distance (0.0 - ...)
* @param filter a pointer to the sound effect filter that should be enabled on the source, 
* <code>NULL</code> is used to play source without any sound effect filter enabled
* @param timeLimit the time in seconds that the sound effect filter should be enabled
* @throws string if the specified buffer is invalid
*/
void Sound::play(SoundBuffer buffer, int priority, const char* cullType, ALfloat* position, ALfloat* velocity, ALboolean loop, ALfloat pitch, ALfloat gain, ALfloat rolloffFactor, SFXFilter* filter, unsigned long timeLimit)
{
	// check if the buffer is valid
	if (!alIsBuffer(buffer))
		errCheck->throwError("AL", "Sound", "play", "invalid buffer");

	// make sure sound is stopped
	stop();

	// attach buffer to sound source
	alSourcei(mSource, AL_BUFFER, buffer);
	errCheck->checkALError("Sound", "play (buffer)");

	// update priority and distance culling type
	mPriority = priority;
	mCullType = cullType;

	// disable any active sound effect filter to make sure that the correct sound properties are
	// stored if enableing a sound effect filter
	disableSFXFilter();

	// set basic sound properties
	setProperties(position, velocity, loop, pitch, gain, rolloffFactor);

	// enable sound effect filter if available
	if (filter)
		enableSFXFilter(filter, timeLimit);

	// play sound
	alSourcePlay(mSource);
	errCheck->checkALError("Sound", "play (play)");
}

/**
* A synonymous function for <code>resume()</code>.
*/
void Sound::play()
{
	resume();
}

/**
* Pause the sound source and the associated timer of an enabled sound effect filter.<br/>
* This function will do nothing if the sound source is already paused or stopped.
*/
void Sound::pause()
{
	if (isPlaying())
	{
		// pause sound source
		alSourcePause(mSource);
		errCheck->checkALError("Sound", "pause");

		// pause sound effect filter timer
		if (mSFXFilter)
			mSFXFilterTimer.stop();
	}
}

/**
* Resume the paused sound and the associated timer of an enabled sound effect filter.<br/>
* This function will do nothing if the sound source is already playing or stopped.
*/
void Sound::resume()
{
	if (isPaused())
	{
		// resume sound source
		alSourcePlay(mSource);
		errCheck->checkALError("Sound", "resume");

		// resume sound effect filter timer
		if (mSFXFilter)
			mSFXFilterTimer.start();
	}
}

/**
* Stops the sound source.<br/>
* This function will do nothing if the sound source is already stopped.
*
* @param source the sound that should be stopped
*/
void Sound::stop()
{
	if (!isStopped())
	{
		alSourceStop(mSource);
		errCheck->checkALError("Sound", "stop");
	}
}


/* sound source state functions */

/**
* Checks if the sound source is playing.
*
* @return <code>true</code> if the sound source is playing, otherwise <code>false</code>
*/
bool Sound::isPlaying()
{
	return getState() == AL_PLAYING;
}

/**
* Checks if the sound source is paused.
*
* @return <code>true</code> if the sound source is paused, otherwise <code>false</code>
*/
bool Sound::isPaused()
{
	return getState() == AL_PAUSED;
}

/**
* Checks if the sound source is stopped.
*
* @return <code>true</code> if the sound source is stopped, otherwise <code>false</code>
*/
bool Sound::isStopped()
{
	return getState() == AL_STOPPED;
}

/**
* Fetches and returns the state of the sound source.
*
* @return the fetched state of the specified source; <code>AL_PLAYING</code>, <code>AL_PAUSED</code>
* or <code>AL_STOPPED</code>
*/
ALint Sound::getState()
{
	ALint state;
	alGetSourcei(mSource, AL_SOURCE_STATE, &state);
	errCheck->checkALError("Sound", "getState");

	return state;
}

/**
* Fetches and returns the priority of the sound source.
*
* @return the priority of the sound source
*/
int Sound::getPriority()
{
	return mPriority;
}

/**
* Fetches and returns the sound type of the sound source.
*
* @return the sound type of the sound source
*/
SoundType Sound::getSoundType()
{
	return mSoundType;
}

/**
* Fetches and returns the sound distance culling type of the sound source.
*
* @return the sound distance culling type of the sound source
*/
const char* Sound::getCullType()
{
	return mCullType;
}

/**
* Fetches and returns the currently used OpenAL sound source.
*
* @return the OpenAL sound source currently used by this object
*/
SoundSource Sound::getSoundSource()
{
	return mSource;
}


/* sound effect filter functions */

/**
* Enables the specified sound effect filter for the specified number of seconds.
*
* @param filter a pointer to the sound effect filter that should be enabled on the source, 
* <code>NULL</code> is a valid value and has the same effect as <code>disableSFXFilter()</code>
* @param timeLimit the time in seconds that the sound effect filter should be enabled
*/
void Sound::enableSFXFilter(SFXFilter* filter, unsigned long timeLimit)
{
	// disable currently enabled sound effect filter
	disableSFXFilter();
	
	if (filter)
	{
		filter->enable(mSource);
		mSFXFilter = filter;
		mSFXFilterTimer.start(timeLimit);
	}
}

/**
* Disables any enabled sound effect filter.
*/
void Sound::disableSFXFilter()
{
	if (mSFXFilter)
	{
		mSFXFilter->disable(mSource);
		mSFXFilter = NULL;
		mSFXFilterTimer.stop();
		mSFXFilterTimer.reset();
	}
}

/**
* Fetches and returns a pointer to the enabled sound effect filter.
*
* @return a pointer to the enabled sound effect filter or <code>NULL</code> if there is no sound
* effect filter enabled
*/
SFXFilter* Sound::hasSFXFilter()
{
	return mSFXFilter;
}


/* misc functions */

/**
* Updates and checks the timer associated with the enabled sound effect filter.<br/>
* This function must be called from the main loop as often as possible (or often enough to not
* compromise sound effect filtering time limits).
*/
void Sound::update()
{
	// check timer associated with the enabled sound effect filter
	if (mSFXFilter && mSFXFilterTimer.isOverLimit())
		disableSFXFilter();
}

/**
* Resets the properties of the sound source and replaces the current sound source with the
* specified sound source to allow reusage of this <code>Sound</code>-object.
*
* @param source the new sound source to replace the current one
*/
void Sound::reset(SoundSource source)
{
	ALboolean isSource = alIsSource(source);
	errCheck->checkALError("Sound", "reset (is source)");

	if (source != NO_SOUND_SOURCE && !isSource)
		errCheck->throwError("Invalid argument", "Sound", "reset (check source)", "invalid sound source");

	mSource			= source;
	mPriority		= 0;
	mCullType		= "";
	mSoundType		= NORMAL_SOUND;
	mSFXFilter		= NULL;
	mSFXFilterTimer.stop();
	mSFXFilterTimer.reset();

	// make sure sound is stopped
	stop();
}